/*
 * This file is part of algowala.
 *
 * algowala is copyright (c) 2012 Jagannathan Sampath <algowala@jagernot.com>
 *
 * algowala is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * algowala is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with algowala.  If not, see <http://www.gnu.org/licenses/>.
 *
*/
#include <fstream>
#include <iostream>
#include "key_consts.h"
#include "input.h"
#include "font.h"
#include "console.h"
#include "basic_viewer.h"

using namespace std;
using namespace boost::posix_time;
using namespace boost::gregorian;

extern console cons;

extern chrono clk;

basic_viewer::basic_viewer (const std::string& settingsf) {
  load (settingsf);
  timelen = get_char_width ("99:99:99.999");
  make_price_list = make_time_list = 0;
}

basic_viewer::basic_viewer () {
  timelen = get_char_width ("99:99:99.999");
}

basic_viewer::~basic_viewer () {
  glDeleteLists (gl_prices, 1);
  glDeleteLists (gl_times, 1);
}

void basic_viewer::obj2win (const point<double>& v, double& wx, double& wy) {
  wx = (int) (win_per_obj.x * v.x);
  wy = (int) (win_per_obj.y * v.y);
}

void basic_viewer::obj2win (const double& ox, const double& oy, double& wx, double& wy) {
  wx = (int) (win_per_obj.x * ox);
  wy = (int) (win_per_obj.y * oy);
}

void basic_viewer::win2obj (const double& wx, const double& wy, double& ox, double& oy) {
  ox = obj_per_win.x * wx;
  oy = obj_per_win.y * wy;
}

void basic_viewer::load (const std::string& _fname) {
  
  fname = _fname;
  ifstream file (fname.c_str (), ios::in);
  if (!file) return;
  load (file);

}

void basic_viewer::load  (ifstream& file) {

  std::string ignore;
	
  file >> ignore >> name;

  double l, b, r,  t;
  file >> ignore >> l >> b >> r >> t;
  win.set (l, b, r, t);

  file >> ignore >> win_chunk.x >> win_chunk.y;
  file >> ignore >> obj_chunk.x >> obj_chunk.y;
  
  calc_win_obj ();

  file >> ignore >> snap_what;

  calc_visual_params ();

}

void basic_viewer::save () {
  ofstream file (fname.c_str(), ios::out);
  save (file);
}

void basic_viewer::save (ofstream& file) {
  file << "editor " << name << endl;
  file << "window " << win.left << ' ' << win.bottom << ' ' << win.right << ' ' << win.top << endl;
  file << "win_chunk " << win_chunk.x << ' ' << win_chunk.y << endl;
  file << "obj_chunk " << obj_chunk.x << ' ' << obj_chunk.y << endl;
  file << "snap " << snap_what << endl;
}

void basic_viewer::calc_win_mouse () {
  win.update_mouse ();
}

void basic_viewer::calc_visual_params () {
  win.calc_viewwin ();
  win.locate_mouse ();
  calc_snaps (snap_what);
  make_price_list = 1;
  make_time_list = 1;
}

void basic_viewer::calc_win_obj () {
  win_per_obj (win_chunk.x / obj_chunk.x, win_chunk.y / obj_chunk.y);
  obj_per_win (obj_chunk.x / win_chunk.x , obj_chunk.y / win_chunk.y);
}

void basic_viewer::calc_snaps (int what) {
  
  snap_what = what;

  int l, r, b, t;

  l = (int)(win.left / win_chunk.x);
  startx = l * win_chunk.x;
  r = (int)(win.right / win_chunk.x);
  endx = r * win_chunk.x;

  b = (int)(win.bottom / win_chunk.y);
  starty = b * win_chunk.y;
  t = (int)(win.top / win_chunk.y);
  endy = t * win_chunk.y;
  
  if (is_snapx()) {
    
    xlines.clear ();
    nxpts = 0;
    int x1 = startx;
    while (x1 <= endx) {
      xlines.push_back (x1);
      xlines.push_back (win.bottom);
      xlines.push_back (x1);
      xlines.push_back (win.top);
      x1 += win_chunk.x;
      nxpts += 2;
    }
    
  }
  
  if (is_snapy ()) {
  
    ylines.clear ();
    nypts = 0;
    int y1 = starty;
    while (y1 <= endy) {
      ylines.push_back (win.left);
      ylines.push_back (y1);
      ylines.push_back (win.right);
      ylines.push_back (y1);
      y1 += win_chunk.y;
      nypts += 2;
    }

  }
  
}

bool basic_viewer::handle_input () {
		
  // movement

  double pan_rept = window::PAN_REPEAT, zoom_rept = window::ZOOM_REPEAT;
  if  (keypressedd (k_a, pan_rept, pan_rept)) {win.panx (-1); calc_visual_params ();}
  else if (keypressedd (k_d, pan_rept, pan_rept)) {win.panx (+1); calc_visual_params ();}
  else if (keypressedd (k_w, pan_rept, pan_rept)) {win.pany (+1); calc_visual_params ();}
  else if (keypressedd (k_s, pan_rept, pan_rept)) {win.pany (-1); calc_visual_params ();}
  else if (keypressedd (k_q, zoom_rept, zoom_rept)) {win.zoom (1); calc_visual_params ();}
  else if (keypressedd (k_e, zoom_rept, zoom_rept)) {win.zoom (-1); calc_visual_params ();}

  // snap
  else if (keypressed (k_x)) calc_snaps (SNAP_X);
  else if (keypressed (k_y)) calc_snaps (SNAP_Y);
  else if (keypressed (k_b)) calc_snaps (SNAP_BOTH);
  else if (keypressed (k_n)) calc_snaps (SNAP_NONE);

  else if (keypressedd (k_f5)) {
    if (keydown (k_lshift)) set_win_chunk (win_chunk.x, --win_chunk.y); else set_win_chunk (--win_chunk.x, win_chunk.y);
  } else if (keypressedd (k_f6)) {
    if (keydown (k_lshift)) set_win_chunk (win_chunk.x, ++win_chunk.y); else set_win_chunk (++win_chunk.x, win_chunk.y);
  }

  return true;
}

void basic_viewer::set_win_chunk (int x, int y) {

  if (x < 1) x = 1;
  if (y < 1) y = 1;

  win_chunk.x = x;
  win_chunk.y = y;
  
  calc_win_obj ();
}

int basic_viewer::is_snapx () {
  return ((snap_what == SNAP_X) || (snap_what == SNAP_BOTH));
}

int basic_viewer::is_snapy () {
  return ((snap_what == SNAP_Y) || (snap_what == SNAP_BOTH));
}

void basic_viewer::snap (double& x, double& y) {
  x = win.mousex;
  y = win.mousey;
  if (is_snapx()) {
    double s = win.mousex / win_chunk.x;
    if (s < 0) s -= 0.5; else s += 0.5;
    x = (int) s * win_chunk.x;
  }
  if (is_snapy()) {
    double s = win.mousey / win_chunk.y;
    if (s < 0) s -= 0.5; else s += 0.5;
    y = (int)s * win_chunk.y;
  }
}

void basic_viewer::project () {
  glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
      glLoadIdentity ();
      glMatrixMode (GL_PROJECTION);
      glPushMatrix ();
        glLoadIdentity ();
        glOrtho (win.left, win.right, win.bottom, win.top, -1, 1);
}

void basic_viewer::unproject () {
    glMatrixMode (GL_PROJECTION);
    glPopMatrix ();
    glMatrixMode (GL_MODELVIEW);
    glPopMatrix ();
}

void basic_viewer::label_y (const string& format) {
  
  if (make_price_list) {

    double y0 = starty, y1 = endy;
    double ox, oy;

    double x = 0;
    int vx, vy;
    extern viewport view;
    
    gl_prices = glGenLists (1);
    glNewList(gl_prices, GL_COMPILE);

    glColor3f (0.3, 0.5, 0.5);
    int lvy = 0, line = get_line_height ();
    while (y0 <= y1) {
      win2obj (x, y0, ox, oy);
      if (oy >= 0) {
        win2view (x, y0, vx, vy, win, view);
        sprintf (buf, format.c_str (), oy);
        if ((vy - lvy) >= line) { 
          draw_string (buf, 2, vy);
          draw_string (buf, view.xmax - get_char_width (buf), vy);
          lvy = vy;
        }
      }
      y0 += win_chunk.y;
    }
    
    glEndList();    

    make_price_list = 0;
  } 
  
  glCallList (gl_prices);
  
}

void basic_viewer::label_x (const ptime& startt, double ox_mult) {

  if (make_time_list) {
    
    extern viewport view;
    
    double x0 = startx, x1 = endx;
    
    double ox, oy, y = 0;
    int vx = 0, vy = 0, pvx = -timelen;
    
    gl_times = glGenLists (1);
    glNewList(gl_times, GL_COMPILE);
      glColor3f (1, 1, 1);
      while (x0 <= x1) {
        win2view (x0, win.bottom, vx, vy, win, view);
        int dvx = vx - pvx;
        if (dvx >= timelen) {
          win2obj (x0, y, ox, oy);
          string str (clk.get_wall_clock (ox * ox_mult, startt));
          draw_string (str, vx, vy);
          draw_string (str, vx, view.ymax - fnt.max_char_height);
          pvx = vx;
        }
        x0 += win_chunk.x;
      }
    glEndList ();
    
    make_time_list = 0;
  }
  
  glCallList (gl_times);
  
}

void basic_viewer::draw  () {
  // must always be wrapped by project, unproject
  //
  draw_snaps ();
}

void basic_viewer::draw_cursor (const string& fmt, const string& str) {

  glMatrixMode (GL_PROJECTION);
  glPushMatrix ();
    glLoadIdentity ();
    extern viewport view;
    glOrtho (0, view.xmax, 0, view.ymax, 0, 1);
    double wx, wy; snap (wx, wy);
    double cx, cy; win2obj (wx, wy, cx, cy);

    glColor3f (1, 1, 1);
    static const int buf_size = 256;
    static char buf [buf_size] = {0};
    
    sprintf (buf, (char *) fmt.c_str(), cy, str.c_str ());
    extern int mousex, mouseyy; 
    draw_string (buf, mousex, mouseyy);
    
  glPopMatrix ();

  glColor3f (0.4, 0.4, 0.4);
  glBegin (GL_LINE_STRIP);
    glVertex2f (win.left, wy);
    glVertex2f (wx, wy);
    glVertex2f (wx, win.bottom);
  glEnd ();

}

void basic_viewer::draw_snaps () {

  //
  // draw snap lines
  //

  static const double sr = 0.1, sg = sr, sb = sg; // snap color

  glColor3f (sr, sg, sb);
  
  glEnableClientState (GL_VERTEX_ARRAY);
  
  if (is_snapx()) { // lines along y
    glVertexPointer (2, GL_INT, 0, xlines.data ());
    glDrawArrays (GL_LINES, 0, nxpts);
  }

  if (is_snapy()) { // lines along x
    glVertexPointer (2, GL_INT, 0, ylines.data ());
    glDrawArrays (GL_LINES, 0, nypts);
  }
  
  glDisableClientState (GL_VERTEX_ARRAY);
  
}

void basic_viewer::hilite_x (double x) {
  glColor3f (0.5, 0.5, 0.5);
  double wx, wy; obj2win (x, wy, wx, wy);
  glBegin (GL_LINES);
    glVertex2d (wx, win.top);
    glVertex2d (wx, win.bottom);
  glEnd ();
  
}

void basic_viewer::hilite_y (double y) {
  glColor3f (0.5, 0.5, 0.5);
  double wx, wy; obj2win (wx, y, wx, wy);
  glBegin (GL_LINES);
    glVertex2d (win.left, wy);
    glVertex2d (win.right, wy);
  glEnd ();
}
